﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YasharEl.Infrastructure.Core.Applications;
using YasharEl.Infrastructure.Core.PersistenceModel.Applications;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.ExceptionHandling;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Repositories;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Specifications;
using YasharEl.Infrastructure.ResourcesManagement;

namespace YasharEl.Infrastructure.ProcessEngine.DomainModel.Implementation
{
    public class DefaultProcessQueryManager : IProcessQueryManager
    {
        #region ReadOnly Fields

        private readonly IDependencyResolver resolver;
        private readonly ILogger<DefaultProcessQueryManager> logger;
        private readonly IExceptionManager exceptionManager;
        private readonly IProcessDefinitionsRepository processDefinitionsRepository;
        private readonly IApplicationsRepository applicationsRepository;
        private readonly IProcessParticipantsRepository participantsRepository;
        private readonly IProcessesRepository processesRepository;

        #endregion

        #region Constructors

        public DefaultProcessQueryManager(
            IDependencyResolver resolver,
            ILogger<DefaultProcessQueryManager> logger, 
            IExceptionManager exceptionManager,
            IProcessDefinitionsRepository processDefinitionsRepository,
            IApplicationsRepository applicationsRepository,
            IProcessParticipantsRepository participantsRepository,
            IProcessesRepository processesRepository)
        {
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(exceptionManager != null, "exceptionManager could not be null.");
            Contract.Requires<ArgumentNullException>(processDefinitionsRepository != null, "processDefinitionsRepository could not be null.");
            Contract.Requires<ArgumentNullException>(applicationsRepository != null, "applicationsRepository could not be null.");
            Contract.Requires<ArgumentNullException>(participantsRepository != null, "participantsRepository could not be null.");
            Contract.Requires<ArgumentNullException>(processesRepository != null, "processesRepository could not be null.");

            this.resolver = resolver;
            this.logger = logger;
            this.exceptionManager = exceptionManager;
            this.processDefinitionsRepository = processDefinitionsRepository;
            this.applicationsRepository = applicationsRepository;
            this.participantsRepository = participantsRepository;
            this.processesRepository = processesRepository;
        }

        #endregion

        #region IProcessQueryManager Members

        public IProcess GetProcessInstance(long applicationId, long userId, Guid workflowInstanceId)
        {
            logger.Debug("Process {0} requested by user {1}...", workflowInstanceId, userId);

            IProcess process = null;

            try
            {
                IApplication application = applicationsRepository.GetById(applicationId);
                if (application == null)
                {
                    throw new ApplicationNotFoundException(applicationId);
                }

                IProcessParticipant user = participantsRepository.GetById(userId);
                if (user == null)
                {
                    throw new ProcessParticipantNotFoundException(
                        resolver.Resolve<IResourceManager>(), userId);
                }

                process = processesRepository
                        .Specify<IProcessSpecification>()
                        .WithWorkflowInstanceId(workflowInstanceId)
                        .ToResult()
                        .SingleOrDefault();

                if (process != null)
                {
                    if (process.ProcessDefinition.BelongsToGroup.BelongsToApplication != application)
                    {
                        logger.Warn("Process {0} requested for incorrect application reference {1} by user {2}.", workflowInstanceId, applicationId, userId);
                        throw new ProcessDefinitionAccessDeniedException(
                            resolver.Resolve<IResourceManager>(),
                            applicationId,
                            process.ProcessDefinition.ProcessDefinitionId
                        );
                    }
                }
            }
            catch (Exception e)
            {
                bool rethrow = exceptionManager.HandleException(e, ProcessEngineConstants.DomainLayerExceptionPolicy);
                if (rethrow)
                {
                    throw;
                }
            }

            return process;
        }

        #endregion
    }
}
